using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using System.Reflection;

using BPMN.MainWindow;

namespace BPMN.MainWindow
{
    public partial class Canvas : UserControl, IUpdateOnEvent
    {
        #region Fields
        Point globalOffset;
        Point offset = new Point(0, 0);
        WindowMain windowMain;
        State state = State.None;
        Color colorBackground = Color.White;
        Pen pen = new Pen(Color.Black);
        SolidBrush solidBrush = new SolidBrush(Variables.SelectFirstColor);
        Selection selectedPool = new Selection(-1, -1);
        Selection selectedFigure = new Selection(-1, -1);
        PointF currentPosition = new Point(-100, -100);
        DragAndDropInfo resizeDragAndDropInfo;
        float initialHeight;
        FigureEvent eventMethod;
        Graphics graphicsForCanvas;
        Color currentFirstColor = Variables.SelectFirstColor;
        Color currentSecondColor = Variables.SelectSecondColor;
        #endregion
        #region Properties
        public Point Offset
        {
            get
            {
                return offset;
            }
            set
            {
                offset = value;
            }
        }
        public State State
        {
            get
            {
                return state;
            }
            set
            {
                state = value;
                windowMain.MouseToolTssl.Text = state.ToString();
                /*
                                for (int i = 0; i < bpmn.Pools.Count; i++)
                                {
                                    if (bpmn.Pools[i].State == FigureState.Selected)
                                    {
                                        bpmn.Pools[i].State = FigureState.Resizing;
                                    }
                                }
                */
                switch (state)
                {
                    case State.Building:
                        SwitchToBuildState();
                        break;
                    case State.Resizing:
                        SwitchToResizeState();
                        break;
                    case State.Selecting:
                        SwitchToSelectState();
                        break;


                }

            }
        }
        public Point GlobalOffset
        {
            get
            {
                return globalOffset;
            }
            set
            {
                globalOffset = value;
            }
        }
        #endregion
        public Pool WMCurrentPool
        {
            get
            {
                return windowMain.BPMNClass.CurrentPool;
            }
            set
            {
                windowMain.BPMNClass.CurrentPool = value;
            }
        }
        public Canvas(WindowMain windowMain)
        {
            InitializeComponent();
            eventMethod = new FigureEvent(update);
            RegisterForReceivingUpdates();
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.windowMain = windowMain;
            WindowMain_ResizeEnd(null, new EventArgs());
            updateBPMN();
            graphicsForCanvas = this.CreateGraphics();
        }

        protected override void OnScroll(ScrollEventArgs se)
        {
            if (se.ScrollOrientation == ScrollOrientation.HorizontalScroll)
            {
                offset.X = -se.NewValue;
            }
            else
            {
                offset.Y = -se.NewValue;
            }
            this.Refresh();
        }
        public void updateBPMN()
        {
            updateBPMNSize();
            this.AutoScrollMinSize = new Size((int)windowMain.BPMNClass.Size.Width, (int)windowMain.BPMNClass.Size.Height);
        }
        private void updateBPMNSize()
        {
            float s = Variables.YOffset * (windowMain.BPMNClass.Pools.Count + 1);
            for (int i = 0; i < windowMain.BPMNClass.Pools.Count; i++)
            {
                s += windowMain.BPMNClass.Pools[i].Size.Height;
            }
            windowMain.BPMNClass.Size = new SizeF(windowMain.BPMNClass.Size.Width, s);
        }
        private void updateCanvasAccordingToBMNP()
        {
            this.AutoScrollMinSize = new Size((int)windowMain.BPMNClass.Size.Width, (int)windowMain.BPMNClass.Size.Height);
        }
        private void updatePoolFromControls(string name)
        {
            if (selectedPool.CurrentSelected >= 0)
            {
                windowMain.BPMNClass.Pools[selectedPool.CurrentSelected].Name = name;
                this.Refresh();
            }
        }
        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics graphics = e.Graphics;
            graphics.SmoothingMode = SmoothingMode.AntiAlias;
            graphics.Clear(colorBackground);
            Draw(graphics, currentFirstColor, currentSecondColor);
            DrawCursor(graphics);
        }
        public void SwitchToResizeState()
        {
            currentFirstColor = Variables.ResizeFirstColor;
            currentSecondColor = Variables.ResizeSecondColor;
        }

        public void SwitchToSelectState()
        {
            currentFirstColor = Variables.SelectFirstColor;
            currentSecondColor = Variables.SelectSecondColor;
        }
        public void SwitchToBuildState()
        {
            currentFirstColor = Variables.BuildFirstColor;
            currentSecondColor = Variables.BuildSecondColor;
        }
        protected void Draw(Graphics graphics, Color firstColor, Color secondColor)
        {
            solidBrush.Color = secondColor;
            for (int i = 0; i < windowMain.BPMNClass.Pools.Count; i++)
            {
                Pool pool = windowMain.BPMNClass.Pools[i];
                if (WMCurrentPool == pool)
                {
                    solidBrush.Color = firstColor;
                    pool.Draw(graphics, pen, solidBrush, offset);
                    solidBrush.Color = secondColor;
                }
                else
                {
                    pool.Draw(graphics, pen, solidBrush, offset);
                }
            }
        }
        protected void DrawCursor(Graphics graphics)
        {
            graphics.DrawLine(pen, currentPosition.X - 5 + offset.X, currentPosition.Y - 5 + offset.Y, currentPosition.X + 5 + offset.X, currentPosition.Y + 5 + offset.Y);
            graphics.DrawLine(pen, currentPosition.X - 5 + offset.X, currentPosition.Y + 5 + offset.Y, currentPosition.X + 5 + offset.X, currentPosition.Y - 5 + offset.Y);
        }
        protected override void OnPaintBackground(PaintEventArgs e)
        {
        }
        protected Figure WhatToSelect(FigureState selectedState, FigureState unselectedState)
        {
            Figure figure = null;
            for (int i = 0; i < windowMain.BPMNClass.Pools.Count; i++)
            {
                Pool pool = windowMain.BPMNClass.Pools[i];
                FigureParentChild fpc = null;
                if (pool.BoundingBox.Contains(currentPosition))
                {
                    windowMain.BPMNClass.CurrentPoolIndex = i;
                    pool.State = selectedState;
                    bool laneFound = false;
                    for (int j = 0; j < windowMain.BPMNClass.CurrentPool.Lanes.Count; j++)
                    {
                        Lane lane = windowMain.BPMNClass.CurrentPool.Lanes[j];
                        if (lane.BoundingBox.Contains(currentPosition))
                        {
                            lane.State = selectedState;
                            fpc = new FigureParentChild(lane.Name, null, FigureType.Lane);
                            EventManagement.SendEventSignal(this, new FigureUpdateEventArgs(fpc, fpc, FigureEventType.Selection, FigureType.Lane));
                            windowMain.BPMNClass.CurrentPool.CurrentLaneIndex = j;
                            laneFound = true;
                            figure = lane;
                        }
                        else
                        {
                            lane.State = unselectedState;
                        }
                    }
                    if (laneFound)
                    {
                        windowMain.poolProperties.Visible = false;
                        windowMain.laneProperties.Visible = true;
                    }
                    else
                    {
                        foreach (Lane lane in pool.Lanes)
                        {
                            lane.State = unselectedState;
                        }
                        fpc = new FigureParentChild(pool.Name, null, FigureType.Pool);
                        EventManagement.SendEventSignal(this, new FigureUpdateEventArgs(fpc, fpc, FigureEventType.Selection, FigureType.Pool));
                        windowMain.poolProperties.Visible = true;
                        windowMain.laneProperties.Visible = false;
                        figure = pool;
                    }
                }
                else
                {
                    pool.State = unselectedState;
                }
            }
            return figure;
        }
        protected override void OnMouseDown(MouseEventArgs e)
        {
            currentPosition = new Point(MousePosition.X - globalOffset.X - offset.X, MousePosition.Y - globalOffset.Y - offset.Y);
            switch (state)
            {
                case State.Selecting:
                    WhatToSelect(FigureState.Selected, FigureState.Unselected);
                    break;
                case State.Resizing:
                    Figure f = WhatToSelect(FigureState.Resizing, FigureState.Unselected);
                    if (f != null)
                    {
                        if (f.GetType() == typeof(Lane))
                        {
                            if ((f as Lane).ResizeBoundingBox.Contains(currentPosition))
                            {
                                this.state = State.ResizeDrawing;
                                Cursor = Cursors.SizeAll;
                                resizeDragAndDropInfo = new DragAndDropInfo(currentPosition);
                                initialHeight = (f as Lane).Size.Height;
                            }
                        }
                    }
                    break;
                case State.Building:
                    WhatToSelect(FigureState.Selected, FigureState.Unselected);
                    if (WMCurrentPool != null)
                    {
                        if (WMCurrentPool.CurrentLane != null)
                        {
                            if (WMCurrentPool.CurrentLane.BoundingBox.Contains(currentPosition))
                            {
                                FlowObject flowObject = null;
                                Type t = Type.GetType("BPMN.MainWindow."+windowMain.ShapeTypeName);
                               ConstructorInfo[] constructors= t.GetConstructors();
                               foreach (ConstructorInfo constructor in constructors)
                               {
                                  object[] attribute= constructor.GetCustomAttributes(typeof(ConstructorAttribute), false);
                                  if (attribute.Length > 0)
                                  {
                                      object[] parameters=new object[3];
                                      parameters[0]=currentPosition;
                                      parameters[1]=new SizeF(30,30);
                                      parameters[2]=FigureState.Unselected;
                                      flowObject = constructor.Invoke(parameters) as FlowObject;
                                      Region poolRegion = GetObjectsRegion(WMCurrentPool.CurrentLane.FlowObjects);
                                      poolRegion.Intersect(flowObject.Region);
                                      Region regionLane = WMCurrentPool.CurrentLane.Region.Clone() as Region;
                                      regionLane.Intersect(flowObject.Region);
                                      if (poolRegion.IsEmpty(graphicsForCanvas) && flowObject.Region.Equals(regionLane, graphicsForCanvas))
                                      {
                                          WMCurrentPool.CurrentLane.FlowObjects.Add(flowObject);
                                      }
                                  }
                               }
                                
                            }
                        }
                    }
                    break;
            }
            this.Refresh();
        }
        protected Region GetObjectsRegion(FlowObjectList objects)
        {
            Region region = new Region();
            region.MakeEmpty();
            foreach (FlowObject laneFlowObject in objects)
            {
                region.Union(laneFlowObject.Region);
            }
            return region;
        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            switch (state)
            {
                case State.ResizeDrawing:
                    currentPosition = new Point(MousePosition.X - globalOffset.X - offset.X, MousePosition.Y - globalOffset.Y - offset.Y);
                    if (WMCurrentPool != null)
                    {
                        Lane lane = WMCurrentPool.CurrentLane;
                        resizeDragAndDropInfo.EndPoint = currentPosition;
                        float aux = resizeDragAndDropInfo.EndPoint.Y - resizeDragAndDropInfo.StartPoint.Y;
                        float lastAux = lane.Size.Height - initialHeight;
                        float currentSize = initialHeight + aux;
                        if (currentSize > Variables.DefaultHeight)
                        {
                            Region r = GetObjectsRegion(lane.FlowObjects);
                            Region r1=r.Clone() as Region;
                            Region resizeRegion = new Region(new RectangleF(lane.Location, new SizeF(lane.Size.Width, currentSize)));
                            r.Intersect(resizeRegion);
                            if (r.Equals(r1, graphicsForCanvas))
                            {
                                lane.Size = new SizeF(lane.Size.Width, initialHeight + aux);
                                for (int i = windowMain.BPMNClass.CurrentPool.CurrentLaneIndex + 1; i < windowMain.BPMNClass.CurrentPool.Lanes.Count; i++)
                                {
                                    windowMain.BPMNClass.CurrentPool.Lanes[i].Location = new PointF(windowMain.BPMNClass.CurrentPool.Lanes[i].Location.X, windowMain.BPMNClass.CurrentPool.Lanes[i].Location.Y - lastAux + aux);
                                }
                                lane.ParentPool.Size = new SizeF(lane.ParentPool.Size.Width, lane.ParentPool.Size.Height - lastAux + aux);
                                for (int i = windowMain.BPMNClass.CurrentPoolIndex + 1; i < windowMain.BPMNClass.Pools.Count; i++)
                                {
                                    windowMain.BPMNClass.Pools[i].Location = new PointF(windowMain.BPMNClass.Pools[i].Location.X, windowMain.BPMNClass.Pools[i].Location.Y - lastAux + aux);
                                }
                                updateBPMN();
                                Refresh();
                            }
                        }
                    }
                    break;
                case State.Drawing:
                    Pool currentPool = windowMain.BPMNClass.CurrentPool;
                    if (currentPool != null)
                    {
                        if (currentPool.CurrentLane != null)
                        {
                            currentPosition = new Point(MousePosition.X - globalOffset.X - offset.X, MousePosition.Y - globalOffset.Y - offset.Y);
                            foreach (FlowObject flowObject in currentPool.CurrentLane.FlowObjects)
                            {
                                if (flowObject.Contains(currentPosition))
                                {
                                    flowObject.State = FigureState.Editing;
                                }
                                else
                                {
                                    flowObject.State = FigureState.Selected;
                                }
                            }
                            Refresh();
                        }
                    }
                    break;
                case State.Editing:
                    currentPool = windowMain.BPMNClass.CurrentPool;
                    if (currentPool != null)
                    {
                        if (currentPool.CurrentLane != null)
                        {
                            currentPosition = new Point(MousePosition.X - globalOffset.X - offset.X, MousePosition.Y - globalOffset.Y - offset.Y);
                            foreach (FlowObject flowObject in currentPool.CurrentLane.FlowObjects)
                            {
                                if (flowObject.Contains(currentPosition))
                                {
                                    flowObject.State = FigureState.Editing;
                                }
                                else
                                {
                                    flowObject.State = FigureState.Selected;
                                }
                            }
                            Refresh();
                        }
                    }
                    break;
            }
        }
        protected override void OnMouseUp(MouseEventArgs e)
        {
            switch (this.state)
            {
                case State.ResizeDrawing:
                    this.state = State.Resizing;
                    break;
                default:
                    break;
            }
            Cursor = Cursors.Default;
            this.Refresh();
        }
        public void WindowMain_ResizeEnd(object sender, EventArgs e)
        {
            offset = new Point(windowMain.Location.X, windowMain.Location.Y);
            offset.Offset(this.Location);
            this.Refresh();
        }

        #region IUpdateOnEvent Members
        public void RegisterForReceivingUpdates()
        {
            EventManagement.GetInstance().RegisterNewModule(FigureType.Lane, new ModuleEventStruct(this, eventMethod));
            EventManagement.GetInstance().RegisterNewModule(FigureType.Pool, new ModuleEventStruct(this, eventMethod));
        }
        public void update(object sender, FigureUpdateEventArgs args)
        {
            if (sender != null && args != null)
            {
                switch (args.Type)
                {
                    case FigureEventType.Modified:
                        /* string figureType=args.OldFigure.GetType().Name.ToString();
                         switch (figureType)
                         {
                             case "Pool":
                                 for(int i=0;i<this.windowMain.BPMN.Pools.Count;i++)
                                 {
                                     if (this.windowMain.BPMN.Pools[i].Name == args.OldFigureName)
                                     {
                                         this.windowMain.BPMN.Pools[i] = (Pool)args.NewFigure;
                                     }
                                 }
                                 break;
                         }*/
                        break;
                    case FigureEventType.Selection:
                        switch (args.FigureType)
                        {
                            case FigureType.Pool:
                                selectedPool.CurrentSelected = windowMain.BPMNClass.Pools.GetIndex(args.NewFigure.Name);
                                currentPosition = windowMain.BPMNClass.Pools[selectedPool.CurrentSelected].Location;
                                break;
                            case FigureType.Lane:
                                selectedPool.CurrentSelected = windowMain.BPMNClass.Pools.GetIndex(args.NewFigure.Name);
                                currentPosition = windowMain.BPMNClass.Pools[selectedPool.CurrentSelected].Location;

                                break;
                        }
                        break;
                }
            }
            updateBPMN();
            Refresh();
        }
        #endregion
    }
    public struct DragAndDropInfo
    {
        PointF startPoint;
        PointF endPoint;
        public PointF StartPoint
        {
            get
            {
                return startPoint;
            }
            set
            {
                startPoint = value;
            }
        }
        public PointF EndPoint
        {
            get
            {
                return endPoint;
            }
            set
            {
                endPoint = value;
            }
        }
        public DragAndDropInfo(PointF startPoint)
        {
            this.startPoint = startPoint;
            this.endPoint = new PointF();
        }
    }
    public enum FigureState
    {
        Selected,
        Unselected,
        Resizing,
        Editing
    }
    public struct Selection
    {
        int lastSelected;
        int currentSelected;
        public int LastSelected
        {
            get
            {
                return lastSelected;
            }
            set
            {
                lastSelected = value;
            }
        }
        public int CurrentSelected
        {
            get
            {
                return currentSelected;
            }
            set
            {
                currentSelected = value;
            }
        }
        public Selection(int lastSelected, int currentSelected)
        {
            this.lastSelected = lastSelected;
            this.currentSelected = currentSelected;
        }
    }
    public enum State
    {
        None,
        Selecting,
        Editing,
        Drawing,
        Moving,
        Resizing,
        ResizeDrawing,
        Building
    }
}